<html>
  <head>
    <title>EF Core 集成    最佳实践</title>
  </head>
  <body>
    <script>
            /*
                一、常规做法

                        为每个模块定义一个单独的DbContext接口和类。

                        不要依赖于应用程序开发的延迟加载。

                        不要为DbContext启用延迟加载。
            */

            /*
                二、DbContext 接口

                        为继承自IEfCoreDbContext的DbContext定义一个接口。

                        请将ConnectionStringName属性添加到DbContext接口。

                        仅仅为聚合根将DbSet<TEntity>properties添加到DbContext接口

                          
                            [ConnectionStringName("AbpIdentity")]
                            public interface IIdentityDbContext : IEfCoreDbContext
                            {
                                DbSet<IdentityUser> Users { get; }
                                DbSet<IdentityRole> Roles { get; }
                            }
  
                        不要定义set;用于此接口中的属性。
            */

            /*
                三、 DbContext类

                        从AbpDbContext<TDbContext>类继承DbContext。

                        向DbContext类添加ConnectionStringName属性。

                        为DbContext类实现相应的interface。
                        
                        例子：

                            [ConnectionStringName("AbpIdentity")]
                            public class IdentityDbContext : AbpDbContext<IdentityDbContext>, IIdentityDbContext
                            {
                                public DbSet<IdentityUser> Users { get; set; }
                                public DbSet<IdentityRole> Roles { get; set; }

                                public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
                                    : base(options)
                                {

                                }

                                //code omitted for brevity
                            }
            */

            /*
                四、数据表前缀和框架

                         将静态TablePrefix和Schemaproperties添加到DbContext类。从常量中设置默认值。

                         eg:

                                public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix;
                                public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;

                         始终为模块使用短的TablePrefix值来在共享数据库中创建唯一表名。Abp表前缀为ABP核心模块保留。
                                  
                          将Schema设置为null作为默认值.

            */

            /*
                五、模型映射

                        通过覆盖DbContext的OnModelCreating方法显式配置所有实体。

                        eg:
                                  protected override void OnModelCreating(ModelBuilder builder)
                                  {
                                      base.OnModelCreating(builder);
                                      builder.ConfigureIdentity();
                                  }

                        不要直接在OnModelCreating方法中配置模型。
                        
                        相反，为ModelBuilder创建一个扩展方法。
                        
                        使用配置ModuleName作为方法名称。


                        eg:          
                                    public static class IdentityDbContextModelBuilderExtensions
                                    {
                                        public static void ConfigureIdentity([NotNull] this ModelBuilder builder)
                                        {
                                            Check.NotNull(builder, nameof(builder));

                                            builder.Entity<IdentityUser>(b =>
                                            {
                                                b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users", AbpIdentityDbProperties.DbSchema);
                                                b.ConfigureByConvention();
                                                //code omitted for brevity
                                            });

                                            builder.Entity<IdentityUserClaim>(b =>
                                            {
                                                b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "UserClaims", AbpIdentityDbProperties.DbSchema);
                                                b.ConfigureByConvention();
                                                //code omitted for brevity
                                            });
                                            
                                            //code omitted for brevity
                                        }
                                    }

                          为每个实体映射调用b.ConfigureByConvention();
                                  
            */

            /*
                六、存储库实现

                          ①、从EfCoreRepository<TDbContext, TEntity, TKey>类继承存储库并实现相应的存储库接口。

                                eg:
                                        public class EfCoreIdentityUserRepository
                                            : EfCoreRepository<IIdentityDbContext, IdentityUser, Guid>, IIdentityUserRepository
                                        {
                                            public EfCoreIdentityUserRepository(
                                                IDbContextProvider<IIdentityDbContext> dbContextProvider)
                                                : base(dbContextProvider)
                                            {
                                            }
                                        }
                            
                          ②、使用DbContext接口作为泛型参数，而不是类。

                          ②、使用GetCancellationToken辅助方法将cancellationToken传递给EF Core。

                              eg:
                                    public virtual async Task<IdentityUser> FindByNormalizedUserNameAsync(
                                        string normalizedUserName, 
                                        bool includeDetails = true,
                                        CancellationToken cancellationToken = default)
                                    {
                                        return await (await GetDbSetAsync())
                                            .IncludeDetails(includeDetails)
                                            .FirstOrDefaultAsync(
                                                u => u.NormalizedUserName == normalizedUserName,
                                                GetCancellationToken(cancellationToken)
                                            );
                                    }

                            GetCancellationToken回退到ICancellationTokenProvider.Token，如果调用者代码没有提供取消令牌，则获取取消令牌。

                                为具有子集合的每个聚合根为IQueryable<TEntity>创建IncludeDetails扩展方法。
                                  
                                示例：

                                    public static IQueryable<IdentityUser> IncludeDetails(
                                        this IQueryable<IdentityUser> queryable,
                                        bool include = true)
                                    {
                                        if (!include)
                                        {
                                            return queryable;
                                        }

                                        return queryable
                                            .Include(x => x.Roles)
                                            .Include(x => x.Logins)
                                            .Include(x => x.Claims)
                                            .Include(x => x.Tokens);
                                    }


                                    在存储库方法中使用IncludeDetails扩展方法，就像在上面的示例代码中使用的那样（参见FindByNormalizedUserNameAsync）。

                                    执行覆盖具有子集合的聚合根的存储库的WithDetails方法。
                                    
                                    示例：

                                          public override async Task<IQueryable<IdentityUser>> WithDetailsAsync()
                                          {
                                              // Uses the extension method defined above
                                              return (await GetQueryableAsync()).IncludeDetails();
                                          }
            */

            /*
                七、模块类

                          为Entity Framework Core集成包定义一个模块类。
                          
                          使用AddAbpDbContext<TDbContext>方法将DbContext添加到IServiceCollection。

                          做将实现的存储库添加到AddAbpDbContext<TDbContext>方法的选项中。
                            
                          例子：

                          [DependsOn(
                              typeof(AbpIdentityDomainModule),
                              typeof(AbpEntityFrameworkCoreModule)
                              )]
                          public class AbpIdentityEntityFrameworkCoreModule : AbpModule
                          {
                              public override void ConfigureServices(ServiceConfigurationContext context)
                              {
                                  context.Services.AddAbpDbContext<IdentityDbContext>(options =>
                                  {
                                      options.AddRepository<IdentityUser, EfCoreIdentityUserRepository>();
                                      options.AddRepository<IdentityRole, EfCoreIdentityRoleRepository>();
                                  });
                              }
                          }
            */
    </script>
     
  </body>
</html>